package com.green_reading.webrtc_collider;

import  java.util.Date;
import java.text.SimpleDateFormat;

import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;

import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONArray;

import org.java_websocket.WebSocket;
import org.java_websocket.WebSocketImpl;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_17;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class ColliderServer extends WebSocketServer {
    private Map<String, WebSocket> users = new ConcurrentHashMap<>();
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
    private long clean_not_active_connection_timestamp = 0;

    public static Connection getDBConnection() {
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    public boolean validDeviceName(String name) {
        try {
            Connection mysql_conn = getDBConnection();
            String query_sql = "select * from device_webrtc where device_name = ? limit 1";
            PreparedStatement preparedStatement = mysql_conn.prepareStatement(query_sql);
            preparedStatement.setString(1, name);
            ResultSet queryResult = preparedStatement.executeQuery();
            while (queryResult.next()) {
                return true;
            }
        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        }
        return false;
    }

    private void remove_user(String name) {
        System.out.println("remove_user: " + name);
        users.remove(name);
    }

    private void remove_multi_user(Set<String> not_active_users_name) {
        for(String key : not_active_users_name) {
            users.remove(key);
            System.out.println("remove_multi_user: " + key);
        }
    }

    private Set<String> clean_not_active_connection(){
        Timestamp ts;
        long timestamp;
        ts = new Timestamp(System.currentTimeMillis());
        timestamp =  ts.getTime() / 1000;
        Set<String> not_active_users_name = new HashSet<>();
        for(String key : users.keySet()) {
            WebSocket conn_ = users.get(key);
            if(key == null) {
                System.out.println("user leave!!!!");
            } else {
                if(conn_.get_last_active_time() + 30L < timestamp) {
                    System.out.println("user not active remove it, " + key);
                    not_active_users_name.add(key);
                    removeConnectPeer(conn_);
                }
            }
        }
        remove_multi_user(not_active_users_name);
        /*
        for(String key : users.keySet()) {
            WebSocket conn_ = users.get(key);
            if(key == null) {
                System.out.println("user leave!!!!");
            } else {
                String[] peer_names = conn_.get_peer_name();
                if(peer_names.length > 0 && users.get(peer_names[0]) == null){
                    conn_.remove_peer_name(peer_names[0]);
                }
            }
        }
        */
        return not_active_users_name;
    }

    public ColliderServer(int port , Draft d) throws UnknownHostException {
        super(new InetSocketAddress(port), Collections.singletonList(d));
    }

    @Override
    public void onOpen(WebSocket conn, ClientHandshake handshake) {
        //System.out.println("User connected");
    }

    @Override
    public void onClose(WebSocket conn, int code, String reason, boolean remote) {
        //System.out.println(java.lang.Thread.currentThread().getId());
        if(users.containsKey(conn.get_connection_name())) {
            remove_user(conn.get_connection_name());
            System.out.println("closed");
            String[] peer_names = conn.get_peer_name();
            for(int i = 0; i < peer_names.length; i++) {
                if(!peer_names[i].equals("")) {
                    WebSocket conn_peer = users.get(peer_names[i]);
                    if (conn_peer != null) {
                        System.out.println("onClose Disconnecting from: " + peer_names[i]);
                        conn_peer.send("{\"type\":\"leave\", \"from\":\"" +
                                conn.get_connection_name() + "\"}");
                        conn_peer.remove_peer_name(conn.get_connection_name());
                    } else {
                        //System.out.println("User have not connected yet");
                    }
                }
            }
        } else {
            //System.out.println("User have not connected yet");
        }
    }

    @Override
    public void onError(WebSocket conn, Exception ex) {
        System.out.println("Error:");
        ex.printStackTrace();
    }

    private void updateLastActiveTime(WebSocket conn) {
        Timestamp ts = new Timestamp(System.currentTimeMillis());
        long timestamp =  ts.getTime() / 1000;
        conn.set_last_active_time(timestamp);
    }

    private void updateLoginTime(WebSocket conn) {
        Timestamp ts = new Timestamp(System.currentTimeMillis());
        long timestamp =  ts.getTime() / 1000;
        conn.set_login_time(timestamp);
    }

    private void removeConnectPeer(WebSocket conn) {
        String[] peer_names = conn.get_peer_name();
        for(int i = 0; i < peer_names.length; i++) {
            if(!peer_names[i].equals("")) {
                WebSocket conn_peer = users.get(peer_names[i]);
                if(conn_peer != null) {
                    conn_peer.remove_peer_name(conn.get_connection_name());
                    conn_peer.send("{\"type\":\"leave\", \"payload\":\"\", \"from\":\"" +
                                   conn.get_connection_name() + "\"}");
                    conn.send("{\"type\":\"leave\", \"payload\":\"\", \"from\":\"" +
                                          conn_peer.get_connection_name() + "\"}");
                }
                conn.remove_peer_name(peer_names[i]);
            }
        }
    }

    private void sendReconnectMessage(WebSocket conn, WebSocket old_conn) {
        Timestamp ts = new Timestamp(System.currentTimeMillis());
        long timestamp =  ts.getTime() / 1000;
        String[] peer_names = old_conn.get_peer_name();
        for(int i = 0; i < peer_names.length; i++) {
            WebSocket peer_conn = users.get(peer_names[i]);
            if(peer_conn != null) {
                if(timestamp - peer_conn.get_last_active_time() < 15) {
                    peer_conn.send("{\"type\":\"offer-loopback-reconnect\", \"payload\":\"\"" +
                                   ",\"from\":\"" + conn.get_connection_name() + "\"}");
                }
            }
        }
    }
    
    @Override
    public void onMessage(WebSocket conn, String message_string) {
        //System.out.println(java.lang.Thread.currentThread().getId());
        Timestamp ts = new Timestamp(System.currentTimeMillis());
        long timestamp =  ts.getTime() / 1000;
        if(timestamp - clean_not_active_connection_timestamp > 5) {
            clean_not_active_connection();
            clean_not_active_connection_timestamp = timestamp;
        }
        try {
            JSONObject data = new JSONObject(message_string);
            String type = data.getString("type");
            String name = data.optString("name");
            JSONObject payload = data.optJSONObject("payload");
            WebSocket conn_peer = null;
            if(!type.equals("ping-signal ")){
                Date data_now = new Date( );
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
                System.out.println("[" + simpleDateFormat.format(data_now) + "] got message: " + message_string);
            }
            switch(type){
            case "ping-signal":
                updateLastActiveTime(conn);
                conn.send("{\"type\":\"pong-signal\", \"from\":\"\"}");
                break;

            case "login":
                //System.out.println("validDeviceName: " + validDeviceName("abc"));
                if(name.equals("")) {
                    conn.send("{\"type\":\"login\", \"success\":\"false\"}");
                }
                WebSocket old_conn = users.get(name);
                //if anyone is logined with same username then check the old connection whether active
                if(old_conn != null) {
                    remove_user(name);
                    conn.set_connection_name(name);
                    users.put(name, conn);
                    conn.send("{\"type\":\"login\", \"success\":\"true\"}");
                    updateLoginTime(conn);
                    updateLastActiveTime(conn);
                    System.out.println("User logged already and not active, remove it: " + name);

                } else {
                    conn.set_connection_name(name);
                    users.put(name, conn);
                    conn.send("{\"type\":\"login\", \"success\":\"true\"}");
                    updateLoginTime(conn);
                    updateLastActiveTime(conn);
                    //System.out.println("User logged: " + name);
                }
                System.out.println("users: " + users.toString());
                break;

            case "query_robot":
                //System.out.println("validDeviceName: " + validDeviceName("abc"));
                if(name.equals("")) {
                    conn.send("{\"type\":\"query_robot\", \"success\":\"false\", \"peer\":\"\"}");
                }
                WebSocket robot_conn = users.get(name);
                //if anyone is logined with same username then check the old connection whether active
                if(robot_conn != null) {
                    String[] peer_names = robot_conn.get_peer_name();
                    if(peer_names[0].equals("")){
                        conn.send("{\"type\":\"query_robot\", \"success\":\"true\", \"peer\":\"\"}");
                    } else {
                        WebSocket robot_conn_peer = users.get(peer_names[0]);
                        if(robot_conn_peer == null){
                            robot_conn.remove_peer_name(peer_names[0]);
                            conn.send("{\"type\":\"query_robot\", \"success\":\"true\", \"peer\":\"\"}");
                        } else {
                            if(robot_conn_peer.get_last_active_time() + 15L < timestamp){
                                robot_conn.send("{\"type\":\"leave\", \"from\":\"" + peer_names[0] + "\"}");
                                remove_user(peer_names[0]);
                                robot_conn.remove_peer_name(peer_names[0]);
                                conn.send("{\"type\":\"query_robot\", \"success\":\"true\", \"peer\":\"\"}");
                            } else if(robot_conn.get_last_active_time() + 15L < timestamp){
                                robot_conn_peer.send("{\"type\":\"leave\", \"from\":\"" + name + "\"}");
                                remove_user(name);
                                robot_conn_peer.remove_peer_name(name);
                                conn.send("{\"type\":\"query_robot\", \"success\":\"true\", \"peer\":\"\"}");
                            } else {
                                conn.send("{\"type\":\"query_robot\", \"success\":\"false\", \"peer\":\"" +
                                        peer_names[0] + "\"}");
                            }
                        }
                    }
                } else {
                    conn.send("{\"type\":\"query_robot\", \"success\":\"false\", \"peer\":\"\"}");
                }
                break;

            case "message":
                conn_peer = users.get(name);
                String message_content = data.optString("payload");
                message_content = message_content.replace("\"", "\\\"");
                System.out.println(message_content);
                if(conn_peer != null) {
                    conn_peer.send("{\"type\":\"message\", \"payload\":\"" +
                                    message_content + "\", \"from\":\"" +
                                    conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"message\", \"payload\":\"" +
                              message_content + "\", \"from\":\"\"}");
                }
                break;

            case "toast-info":
                conn_peer = users.get(name);
                if(conn_peer != null) {
                    String message_content_str = data.optString("payload");
                    //message_content_str = message_content_str.replace("\"", "\\\"");
                    System.out.println(message_content_str);
                    conn_peer.send("{\"type\":\"toast-info\", \"payload\":" +
                                    message_content_str + ", \"from\":\"" +
                                    conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"\", \"payload\":\"\", \"from\":\"\"}");
                }
                break;
               
            case "file-transform":
                conn_peer = users.get(name);
                if(conn_peer != null) {
                    conn_peer.send("{\"type\":\"file-transform\", \"payload\": " +
                                    payload.toString() + ",\"from\":\"" +
                                    conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"file-transform\", \"payload\":\"\", \"from\":\"\"}");
                }
                break;
   
            case "file-transform-parameter":
                conn_peer = users.get(name);
                if(conn_peer != null) {
                    conn_peer.send("{\"type\":\"file-transform-parameter\", \"payload\": " +
                                    payload.toString() + ",\"from\":\"" +
                                    conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"\", \"payload\":\"\", \"from\":\"\"}");
                }
                break;
   
            case "file-transform-parameter-accept":
                conn_peer = users.get(name);
                if(conn_peer != null) {
                    conn_peer.send(
                        "{\"type\":\"file-transform-parameter-accept\", \"payload\":\"\"" +
                        ",\"from\":\"" + conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"\", \"payload\":\"\", \"from\":\"\"}");
                }
                break;

            case "file-transform-file-size-zero":
                conn_peer = users.get(name);
                if(conn_peer != null) {
                    conn_peer.send(
                        "{\"type\":\"file-transform-file-size-zero\", \"payload\":\"\"" +
                        ",\"from\":\"" + conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"\", \"payload\":\"\", \"from\":\"\"}");
                }
                break;

            case "save-data-over":
                conn_peer = users.get(name);
                if(conn_peer != null) {
                    conn_peer.send("{\"type\":\"save-data-over\", \"payload\":\"\"" +
                                    ",\"from\":\"" + conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"\", \"payload\":\"\", \"from\":\"\"}");
                }
                break;

            case "file-transform-cancel":
                conn_peer = users.get(name);
                if(conn_peer != null) {
                    conn_peer.send("{\"type\":\"file-transform-cancel\", \"payload\":" + payload.toString() +
                                    ",\"from\":\"" + conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"\", \"payload\":\"\", \"from\":\"\"}");
                }
                break;

            case "offer-loopback":
                conn_peer = users.get(name);
                if(conn_peer != null) {
                    conn_peer.send("{\"type\":\"offer-loopback\", \"payload\":\"\"" +
                                    ",\"from\":\"" + conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"\", \"payload\":\"\", \"from\":\"\"}");
                }
                break;
               
            case "peer-excced-3":
                conn_peer = users.get(name);
                if(conn_peer != null) {
                    conn_peer.send("{\"type\":\"peer-excced-3\", \"payload\":\"\"" +
                                    ",\"from\":\"" + conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"\", \"payload\":\"\", \"from\":\"\"}");
                }
                break;

            case "offer":
                //for ex. UserA wants to call UserB
                //if UserB exists then send him offer details
                conn_peer = users.get(name);
                if(conn_peer != null) {
                    //setting that UserA connected with UserB
                    conn.add_peer_name(name);
                    conn_peer.send("{\"type\":\"offer\", \"payload\": " +
                                    payload.toString() + ",\"from\":\"" +
                                    conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"\", \"payload\":\"\", \"from\":\"\"}");
                }
                break;
            case "answer":
                conn_peer = users.get(name);
                if(conn_peer != null) {
                    conn.add_peer_name(name);
                    conn_peer.send("{\"type\":\"answer\", \"payload\": " +
                                    payload.toString() + ",\"from\":\"" +
                                    conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"\", \"payload\":\"\", \"from\":\"\"}");
                }
                break;
            case "candidate":
                conn_peer = users.get(name);
                if(conn_peer != null) {
                    conn_peer.send("{\"type\":\"candidate\", \"payload\": " +
                                    payload.toString() + ",\"from\":\"" +
                                    conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"\", \"payload\":\"\", \"from\":\"\"}");
                }
                break;
            case "candidate_list":
                conn_peer = users.get(name);
                JSONArray payload_list = data.optJSONArray("payload");
                if(conn_peer != null || payload_list != null) {
                    conn_peer.send("{\"type\":\"candidate_list\", \"payload\": " +
                                    payload_list.toString() + ",\"from\":\"" +
                                    conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"\", \"payload\":\"\", \"from\":\"\"}");
                }
                break;
            case "leave":
                System.out.println("leave Disconnecting from: " + name);
                conn_peer = users.get(name);
                if(conn_peer != null) {
                    conn_peer.remove_peer_name(conn.get_connection_name());
                    conn.remove_peer_name(conn_peer.get_connection_name());
                    System.out.println("leave Disconnect : " + conn.get_connection_name() + " " + conn_peer.get_connection_name());
                    conn_peer.send("{\"type\":\"leave\", \"payload\":\"\", \"from\":\"" +
                            conn.get_connection_name() + "\"}");
                } else {
                    conn.send("{\"type\":\"peer-not-exist\", \"return_type\":\"\", \"payload\":\"\", \"from\":\"\"}");
                }
                break;
            case "clean-not-active-users":
                Set<String> not_active_users_name = clean_not_active_connection();
                conn.send(not_active_users_name.toString());
                break;
            case "get_online_users_info":
                JSONObject ret = new JSONObject();
                for(String key : users.keySet()) {
                    WebSocket user_conn = users.get(key);
                    if(user_conn != null) {
                        JSONObject user_info = new JSONObject();
                        user_info.put("name", user_conn.get_connection_name());
                        user_info.put("login_time", user_conn.get_login_time());
                        user_info.put("peer_name", user_conn.get_peer_name());
                        user_info.put("last_active_time", user_conn.get_last_active_time());
                        ret.put(key, user_info);
                    } else {
                        System.out.println("user not exist: " + key);
                    }
                }
                conn.send(ret.toString());
                break;
            case "search_online_user":
                JSONObject result = new JSONObject();
                String search_user_name = data.optString("payload");
                for(String key : users.keySet()) {
                    if(key.contains(search_user_name)) {
                        WebSocket user_conn = users.get(key);
                        if(user_conn != null) {
                            JSONObject user_info = new JSONObject();
                            user_info.put("name", user_conn.get_connection_name());
                            user_info.put("login_time", user_conn.get_login_time());
                            user_info.put("peer_name", user_conn.get_peer_name());
                            user_info.put("last_active_time", user_conn.get_last_active_time());
                            result.put(key, user_info);
                        } else {
                            System.out.println("user not exist: " + key);
                        }
                    }
                }
                conn.send(result.toString());
                break;
            case "delete_online_user":
                String delete_user_name = data.optString("payload");
                WebSocket delete_user_conn = users.get(delete_user_name);
                if(delete_user_conn != null) {
                    removeConnectPeer(delete_user_conn);
                    delete_user_conn.send(
                            "{\"type\":\"server_kick_out\", \"payload\":\"\", \"from\":\"\"}");
                    remove_user(delete_user_name);
                    conn.send(
                            "{\"type\":\"message\", \"payload\":\"user deleted\", \"from\":\"\"}");
                    delete_user_conn.close();
                } else {
                    System.out.println("user not exist");
                    conn.send(
                            "{\"type\":\"message\", \"payload\":\" user not exist\", \"from\":\"\"}");
                }
                break;
            default:
                conn.send("{\"type\":\"error\", \"payload\":\"Command not found: " +
                          message_string + " \",\"from\":\"\"}");
                break;
            }

        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onMessage(WebSocket conn, ByteBuffer blob) {
        System.out.println("blob message");
    }

    public static void main(String[] args) throws  UnknownHostException {
        WebSocketImpl.DEBUG = false;
        int port;
        try {
            port = new Integer(args[ 0 ]);
        } catch (Exception e) {
            System.out.println("No port specified. Defaulting to 3000");
            port = 3000;
        }
        new ColliderServer(port, new Draft_17()).start();
    }
}
